using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

//using Athena.Objects;

namespace Athena.Core
{
    /// <summary>
    /// An Item is an abstract class that represents "anything" (sorry, but that is best description)
    /// An Item must have a unique ID if it resides inside a Container
    /// An Item will generally have a Type
    /// An Item can have a Parent (interpretation of "Parent" depends on implementation, but Parents are 
    /// usually Container classes such as Compartments or Modules)
    /// Item provides ways to get the Root parent as well has parent of a particular class. Since Modules
    /// are important, ParentModule is provided to get the Module that the Item belongs in.
    /// Nodes, Edges, and Containers are the three main branches or Item. 
    /// </summary>
    [Serializable()]
    public abstract class Item : ICloneable
    {
        /// <summary>
        /// The GUI object corresponding to this item
        /// </summary>
        protected Objects.IDrawable _Drawable;

        /// <summary>
        /// The GUI object corresponding to this item
        /// </summary>
        public virtual Objects.IDrawable Drawable
        {
            get
            {
                return _Drawable;
            }
            set
            {
                if (value == _Drawable) return;

                if (value == null && _Drawable != null)
                {
                    Objects.IDrawable temp = _Drawable;
                    _Drawable = null;
                    temp.Item = null;
                    temp.Remove();
                }
                else
                {
                    _Drawable = value;
                    if (value.Item != this)
                        value.Item = this;
                }
            }
        }

        /// <summary>
        /// Generate the GUI object for this item
        /// </summary>
        /// <param name="point"></param>
        public abstract void GenerateDrawable(System.Drawing.PointF point);

        protected string _ID = "", _DBID = "", _Type = "";
        
        protected Item _Parent;

        /// <summary>
        /// Get the Module that this item is a contained in (may not be the item's immediate parent)
        /// </summary>
        public virtual Module ParentModule
        {
            get
            {
                Item parent = Parent;

                while (parent != null && !(parent is Module))
                    parent = parent.Parent;

                if (parent is Module)
                    return (Module)parent;
                else
                    return null;
            }
        }

        /// <summary>
        /// Get the parent (or nth parent) that is of the given type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public virtual Item ParentOfType(Type type)
        {
            Item parent = Parent;

            while (parent != null && !(parent.GetType().Equals(type)))
                parent = parent.Parent;

            return parent;
        }

        /// <summary>
        /// Gets the Module that both this item and the other item belong in, i.e. finds the
        /// smallest common module
        /// </summary>
        /// <param name="other">another item</param>
        /// <returns>null if no common module</returns>
        public virtual Module SharedModule(Item other)
        {
            if (other == null) return null;

            Module m1 = ParentModule;
            if (this is Module) m1 = this as Module;

            while (m1 != null)
            {
                if (m1.IsParentOf(other)) return m1;
                m1 = m1.ParentModule;
            }
            return null;
        }

        /// <summary>
        /// Get the parent for this item
        /// </summary>
        /// <returns></returns>
        public virtual Item Parent
        {
            get
            {
                return _Parent;
            }
            set
            {
                Item newparent = value;
                if (_Parent == newparent || IsParentOf(newparent)) return;

                Item oldParent = _Parent;
                _Parent = newparent;
                if (newparent is Container && !((Container)newparent).LocalItems.Contains(this))
                {
                    ((Container)newparent).AddItem(this, oldParent);
                }
                if (oldParent != newparent && oldParent is Container && ((Container)oldParent).LocalItems.Contains(this))
                {
                    ((Container)oldParent).RemoveItem(this, newparent);
                }
            }
        }

        /// <summary>
        /// Checks whether this item is a parent (or parent's parent's...parent) of 
        /// the given item
        /// </summary>
        /// <param name="child">Item to check</param>
        /// <returns></returns>
        public virtual bool IsParentOf(Item child)
        {
            if (child == null) return false;
            if (child == this) return false;

            Item parent = child.Parent;
            while (parent != null)
            {
                if (parent == this) return true;
                parent = parent.Parent;
            }
            return false;
        }

        /// <summary>
        /// stores the most recent error message to be reported
        /// </summary>
        protected string _LastErrorMessage = "";

        /// <summary>
        /// stores the most recent error message to be reported
        /// </summary>
        public string LastErrorMessage
        {
            get { return _LastErrorMessage; }
            set { _LastErrorMessage = value; }
        }

        /// <summary>
        /// checks whether an ID is valid
        /// A valid ID can contain only letters, numbers, or underscore
        /// the ID must not begin with a number
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static bool ValidID(string id)
        {
            if (id.Length < 1) return false;
            char[] chars = id.ToCharArray();
            if (char.IsDigit(chars[0])) return false;
            foreach (char c in chars)
            {
                if (!(char.IsLetterOrDigit(c) || c == '_')) return false;
            }
            return true;
        }

        /// <summary>
        /// Get or Set the ID for this item
        /// </summary>
        public virtual string ID
        {
            get { return _ID; }
            set 
            {
                if (_ID == value) return;

                if (!ValidID(value)) return;

                if (Parent is Container)
                {
                    string oldID = _ID;
                    _ID = ((Container)Parent).RequestNewID(value);
                    ((Container)Parent).ChangeID(this, oldID, _ID);
                }
                else
                    _ID = value;

                if (_Drawable != null) _Drawable.Name = _ID;
            }
        }

        /// <summary>
        /// The database ID for this item
        /// </summary>
        public virtual string DBID
        {
            get { return _DBID; }
            set { _DBID = value; }
        }
        /// <summary>
        /// Get and set the Type for this item
        /// </summary>
        /// <returns></returns>
        public virtual string ItemType
        {
            get { return _Type; }// GetType().ToString(); }            
            set { _Type = value; }
        }
       
        /// <summary>
        /// Default constructor -- make an item with empty label or type "item" with ID "item#"
        /// </summary>
        public Item() { ID = "item"; _DBID = "";  _Type = "item"; }

        /// <summary>
        /// Make an item with the given ID and of the given type
        /// </summary>
        /// <param name="type"></param>
        /// <param name="id"></param>
        public Item(string type, string id)
        {
            ItemType = type;  
            ID = id;
        }
        /// <summary>
        /// Check whether this item belongs with the same family as the given type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public virtual bool IsA(string type)
        {
            string t = type.ToLower();

            return type.Equals("item") || (ItemType.ToLower()).Equals(type.ToLower());
        }

        /// <summary>
        /// Clean up when deleting this item
        /// </summary>
        public virtual void Remove()
        {
            if (_Parent != null && _Parent is Container)
            {
                ((Container)_Parent).RemoveItem(this);
            }
            if (Drawable != null)
            {
                Objects.IDrawable drawable = Drawable;
                _Drawable = null;
                if (drawable.Item == this)
                    drawable.Item = null;
                //drawable.Remove();
            }
        }

        /// <summary>
        /// returns a string: ID(type)
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return ID + "(" + ItemType + ")";
        }

        /// <summary>
        /// make a duplicate of this item
        /// </summary>
        /// <returns></returns>
        public abstract object Clone();

        /// <summary>
        /// Is this node valid as an input in a reaction with the given inputs and outputs?
        /// </summary>
        /// <param name="inputs">inputs to the reaction</param>
        /// <param name="outputs">outputs to the reaction</param>
        /// <returns></returns>
        public virtual bool ValidReactant(ICollection<Node> inputs, ICollection<Node> outputs)
        {
            return true;
        }
        /// <summary>
        /// Is this node valid as an output in a reaction with the given inputs and outputs?
        /// </summary>
        /// <param name="inputs">inputs to the reaction</param>
        /// <param name="outputs">outputs to the reaction</param>
        /// <returns></returns>
        public virtual bool ValidProduct(ICollection<Node> inputs, ICollection<Node> outputs)
        {
            return true;
        }

        /// <summary>
        /// Parent's parent's parent's....until parent is null
        /// </summary>
        public virtual Item Root
        {
            get
            {
                if (Parent == null)
                    return this;
                
                Item parent = Parent;
                while (parent.Parent != null)
                {
                    parent = parent.Parent;
                }
                return parent;
            }
        }
        /// <summary>
        /// Returns the number of parents (inside one another) this item has
        /// </summary>
        public virtual int Depth
        {
            get
            {
                if (Parent == null)
                    return 0;
                int i = 1;
                Item parent = Parent;
                while (parent.Parent != null)
                {
                    parent = parent.Parent;
                    ++i;
                }
                return i;
            }
        }

        /// <summary>
        /// If implemented, this method will copy some (depending on the implementation) of
        /// the information from the given Item to this item. The "some information" should be
        /// sufficient for the items to "mirror" each other, but not be copies or each other, i.e.
        /// ID's will not be copied
        /// </summary>
        /// <param name="other"></param>
        public virtual void Mirror(Item other)
        {
        }

        /// <summary>
        /// Writes the XML for this class
        /// </summary>
        /// <param name="writer">xml writer</param>
        public virtual void WriteXML(XmlWriter writer)
        {
            writer.WriteStartElement("Item", "");

            writer.WriteStartElement("ID", "");
            writer.WriteString(ID);
            writer.WriteEndElement();

            if (!string.IsNullOrEmpty(DBID))
            {
                writer.WriteStartElement("DBID", "");
                writer.WriteString(DBID + " ");
                writer.WriteEndElement();
            }

            writer.WriteStartElement("ItemType", "");
            writer.WriteString(ItemType);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }

        /// <summary>
        /// Reads the information from an XML file
        /// </summary>
        /// <param name="reader">xlm reader</param>
        public virtual bool ReadXML(XmlReader reader)
        {
            bool read = true;

            if (!(reader.NodeType == XmlNodeType.Element && reader.Name == "Item"))
                read = reader.Read();
            
            if (reader.NodeType == XmlNodeType.Element && reader.Name == "Item")
            {
                read = reader.Read();
                while (read)
                {
                    if (reader.NodeType == XmlNodeType.EndElement)
                        break;

                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        switch (reader.Name)
                        {
                            case "ID":
                                read = reader.Read();
                                if (reader.NodeType == XmlNodeType.Text)
                                    ID = reader.ReadContentAsString();
                                
                                break;
                            case "DBID":
                                read = reader.Read();
                                if (reader.NodeType == XmlNodeType.Text)
                                    DBID = reader.ReadContentAsString().Trim();
                                
                                break;
                            case "ItemType":
                                read = reader.Read();
                                if (reader.NodeType == XmlNodeType.Text)
                                    ItemType = reader.ReadContentAsString();
                                
                                break;
                            default:
                                while (reader.NodeType != XmlNodeType.EndElement)
                                    read = reader.Read();
                                break;
                        }
                    }

                    if (!read || (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Item")) break;

                    if (reader.NodeType != XmlNodeType.Element)
                        read = reader.Read();
                }
                return true;
            }
            return false;
        }
    }
}
